Scriptia. Javascript y buenas prácticas en español



Scriptia / Etiquetas / or

Saltar a Acerca de Scriptia

Estás viendo la página para la etiqueta (o conjunto de etiquetas) or .

Etiquetas relacionadas: |, |

Pruebas unitarias con QUnit

Las pruebas unitarias (unit testing) son necesarias y convenientes, ya programes en Ruby, en PHP, en JavaScript o en Cuenca. En esta notita veremos cómo utilizar QUnit –la biblioteca creada para el testeo del núcleo de jQuery– para testear nuestros propios proyectos.

El documento base

Comencemos pues, por descargar QUnit y preparar un documento HTML con la siguiente estructura:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
  <title>My Test Suite</title>
  <link rel="Stylesheet" media="screen" href="ruta/a/testsuite.css" />
  <script type="text/javascript" src="ruta/a/jquery.js"></script>
  <script type="text/javascript" src="ruta/a/testrunner.js"></script>

  <style type="text/css" media="screen">
  /* <![CDATA[ */
    #main {
      display: none;
    }
  /* ]]> */
  </style>
</head>

<body>
  <h1>My Test Suite</h1>
  <h2 id="banner"></h2>
  <h2 id="userAgent"></h2>

  <div id="main">
  </div>

  <ol id="tests"></ol>
</body>
</html>

El elemento con id="banner" es utilizado por QUnit para mostrar el resultado global de la ejecución de las pruebas. En id="userAgent" se muestra información relativa al navegador utilizado. La lista y el detalle de la ejecución de cada prueba se genera en id="tests".

En caso de disponer de Firebug, QUnit volcará abundante y útil información en la consola.

Si necesitamos un fragmento de HTML para la ejecución de las pruebas, lo incluiremos en id="main". Ojo: antes de la ejecución de cada test se devuelve el contenido de este elemento a su estado inicial, esto es, no se mantienen los eventos asignados ni las modificaciones realizadas sobre el árbol en tests anteriores.

Nuestro primer test

Al ajo. La ejecución de una prueba implica una llamada a la función test, pasando como parámetros una cadena identificativa y la referencia a una función en la que se realizan las comprobaciones (aserciones).

La más sencilla de las comprobaciones es ok. Si el parámetro que recibe evalúa a true, el resultado se considera satisfactorio.

Poniendo los dos últimos párrafos en código:

test('Prueba minimalista', function(){
  ok(1, 'mi mensajito');
});

Incorpora esas líneas a tu documento de pruebas (dentro de un elemento SCRIPT) y échale un vistazo en el navegador. ¿Todo bien?

Comprobaciones varias

Por supuesto disponemos de otros métodos de comprobación de resultados, estos son los fundamentales:

equals(unArgumento, otroArgumento, mensaje)
Comprueba la igualdad de dos argumentos primitivos.

isSet(unArray, otroArray, mensaje)
Comprueba que dos arrays tienen el mismo contenido.

isObject(unObjeto, otroObjeto, mensaje)
Comprueba que dos objetos tienen los mismos valores asignados a las mismas propiedades.

Módulos

Si utilizamos el mismo documento para probar distintas partes de nuestra biblioteca podemos utilizar la función module para dar nombre a las secciones de la suite.

module('Widget de votaciones');
test('tras la inicialización'), function() {
  // inicializamos un widget, etc.
  equals($('a.rate', widget).length, 5, 'contiene 5 estrellicas');
});

test('etcetera', function() {
  // otro test por aquí
});

Expectaciones

Todo lo que puede fallar, falla (de hecho, si practicas el desarrollo basado en pruebas, lo primero que debería hacer cada test es fallar). Para indicar el total de comprobaciones que se realizarán en una prueba, utilizamos la función expect.

module('expectaciones');
test('un test', function() {
  expect(3);
  ok(1, 'una');
  equals(1 + 1, 2, 'dos');
  equals(calculoCasiImposible(), 42, 'y tres');
});

De esta manera, si la ejecución de calculoCasiImposible (la tercera comprobación) falla estrepitosamente, la prueba no se da por buena.

Asincronía y pausas

Si realizamos peticiones asíncronas en nuestros tests, necesitaremos detener la ejecución de la serie y reanudarla cuando convenga. Los métodos stop y start hacen lo que su nombre parece indicar.

module('asincronía');
test('con pausa', function() {
  expect(2);
  ok(1, 'una');
  function my_callback(response, status) {
    start();
    equals(response, '1', 'el servidor devuelve lo que toca');
  }
  // detenemos la ejecución de las pruebas
  stop();
  // lanzamos una petición Ajax
  $.get(some_url, my_callback);
});

Si quieres empaparte de pruebas escritas con y para QUnit, lo mejor es que descargues una una release completa de jQuery y bucees en sus entrañas.

Y esto ha sido todo. La semana que viene más y más sexy (si cabe).

Eventos en jQuery 1.2

Días ha que escribí una nota sobre los eventos en jQuery. Y hora es de ampliar dicho artículo con las novedades que la serie 1.2 de jQuery añade al respecto.

Lo de siempre

Recordemos las bases de la asignación y el manejo de eventos en jQuery. La forma básica de asignar un manejador es la siguiente:

$('un.selector')
  .bind('click', unaFuncion);

El manejador (unaFuncion) recibe como parámetro el objeto que representa el evento (normalizado para tener acceso común a sus propiedades en los distintos navegadores). La función se ejecuta en el contexto del elemento al que se ha asignado el manejador.

function unaFuncion(e) {
    console.log('Contexto', this);
    console.log('Evento', e);
}

$('un.selector')
    .bind('click', unaFuncion);

Lo nuevo

Con jQuery 1.2 podemos asignar un manejador para varios eventos con una sola llamada a bind(). Utilizamos para ello una lista de nombres de evento separados por espacios. Observa que en el manejador tomamos una decisión basada en la propiedad type del objeto del evento.

function handle(e) {
  var $label = $(this).prev();
  if (e.type == 'focus') {
    $label.hide();
  }
  else if (this.value == '') {
    $label.show();
  }
}

$('#login input')
  .filter('[type=text], [type=password]')
    .bind('focus blur', handle);

Otra novedad interesante es la pseudonombrespaciación de eventos, que se puede practicar utilizando el nombre del evento seguido de un punto y un identificador arbitrario al asignar el manejador.

Y esto, ¿para qué? Para facilitar la eliminación de grupos de manejadores.

// en algún lugar del código de un drag'n'drop
$(document)
  .bind('mousemove.rock_n_roll', some_handler)
  .bind('mouseup.rock_n_roll', mouseup_handler);

// en el código de finalización del arrastre
$(document)
  .unbind('.rock_n_roll');

Y algo que no es exactamente nuevo pero mola todo y se conoce poco: podemos pasar un objeto de datos en la asignación. El manejador lo recibirá en la propiedad data del evento.

function handle_click(e) {
  alert(e.data.foo);
}

$.fn.somePlugin(opts) {
  var settings = $.extend({
    foo: 'bar'
  });
  this.bind('click', settings, handle_click);
  return this;
}

// mostrará "bar" al activar los enlaces
// con clase "tigre"
$('a.tigre').somePlugin();

// mostrará "tolo" al activar los enlaces
// con clase "leon"
$('a.leon').somePlugin({ foo: 'tolo' });

Por último, se incorpora a la API el método triggerHandler que complementa a trigger. La diferencia: triggerHandler dispara los manejadores asignados al evento pero no ejecuta las acciones por defecto del navegador.

Para más y mejor información y, de paso, practicar el inglés: Release: jQuery 1.2/Events.

Ajax, eventos y jQuery

Un «problema» con el que todo novato de la programación con jQuery se encuentra tarde o temprano (y las listas de correo lo demuestran) es que el contenido cargado (o generado) dinámicamente no dispara los manejadores de eventos asignados en $(document).ready.

Y, hoygan, esto es de lo más normal.

Si repasamos nuestra forma habitual de trabajar con jQuery, y reflexionamos un poquito…

$(document).ready(function() {
  $('selecciono.algo')
    .bind('click', hazCosasBonitas);
});

… recordaremos que estamos utilizando $(document).ready porque no podemos seleccionar elementos que no existen. De ahí que tengamos que esperar a disponer de un árbol DOM completo. De ahí que los manejadores de eventos no afecten a elementos nuevos.

¿Entonces?

Opciones las hay adecuadas a unos casos y a otros. La más sencilla, para casos sencillos, es inicializar los contenidos nuevos una vez los hayamos insertado en el documento.

Necesitamos, pues, una función que nos permita seleccionar y actuar dentro de un contexto. Recuerda que jQuery provee de selecciones basadas en contexto. Si utilizas

$('a');

seleccionarás todos los enlaces del documento. Pero si usas

$('a', unElementoSelecto);

seleccionarás los enlaces descendientes de unElementoSelecto.

Por tanto, podemos crear nuestra función de inicialización según contexto como sigue.

function initLinks(context) {
  $('a', context)
    .bind('click', hazCosasBonitas)
}

Que ejecutaremos desde nuestro manejador para $(document).ready, pasando el documento como contexto de búsqueda:

$(document).ready(function() {
  initLinks(document);
});

Así que estamos como al principio… pero mucho más cerca del final. Lo único que nos queda por hacer es ejecutar initLinks cuando recuperemos nuevo contenido. Si usamos load, el más simplón de los métodos Ajax de jQuery, basta con usar una función de callback:

function loadNewContent() {
  $('#holder').load('ajax-content.html #response p', function() {
    // `this` apunta al elemento que acabamos de rellenar
    initLinks(this);
  });  
}

Y esta es la idea básica, que he reflejado en esta pequeña demo en latín.

Por supuesto, hay otras opciones. Si eres de los que para clavar un clavo usa un plugin, échale un vistazo a LiveQuery; si tienes un serio trasiego de elementos vivos, mejor apostar por la delegación de eventos. Pero esas son otras historias que contaré a su debido momento.

Taller de jQuery en The Cocktail el 29 de mayo

Los muchachos de The Cocktail me han invitado a impartir un taller de jQuery en The Cocktail Academy.

Será el jueves 29 de mayo de 2008, a partir de las las 19.30h en los locales de Salamanca, 17 (Madrid). Si piensas asistir (es gratis), inscríbete cuanto antes en el wiki de los talleres.

Una pista de por donde irán los tiros. Hablaremos de:

  • selección de elementos, filtrado, etc.;
  • eventos a fondo, incluyendo delegación de eventos y sus ventajas;
  • navegación por el DOM;
  • algún que otro truquito de lo más chachipiruli.

Y por supuesto, de algunas razones para usar (o no usar) jQuery.

Después del taller, estáis todos invitados a pagarme unas cañas.

jQuery API Browser está de vuelta

Para los que preferíamos la vieja interfaz de consulta de la API de jQuery: jQuery API.

Un patrón de desarrollo de plugins para jQuery

Mike Alsup, autor de jQuery form plugin y otras delicias, nos explica cómo crear un plugin para jQuery que cumpla con las condiciones de: no contaminar el espacio de nombres, acepte opciones (y las extienda), mantenga los límites adecuados entre lo público y lo privado y saque provecho del plugin de metadatos. Ahí es nada: A Plugin Development Pattern.

jQuery UI y jQuery 1.2.1

Llega jQuery UI, una colección de componentes reusables y de alta calidad: jQuery UI: Interactions and Widgets.

Y también la versión 1.2.1 de jQuery, con algunas correcciones de bugs sobre la 1.2.

jQuery 1.2

El equipo de desarrollo de jQuery ha publicado la versión 1.2 de la biblioteca. Incorpora algunas novedades que justifican sobradamente el cambio de minor version.

Selectores

Se incorporan :has(), :header y :animated. Desaparecen los selectores XPath (si los necesitas, puedes usar el plugin de compatibilidad con XPath) y, aprovechando la ocasión, la sintaxis para los selectores por atributo usa sintaxis CSS. Así, a[@class=jfgi] se convierte en a[class=jfgi].

Atributos

El método val() ha sido mejorado y ahora permite recuperar el valor de elementos SELECT y marcar y desmarcar checkboxes.

Navegación por el DOM

Nuevos métodos. map() permite la transmutación alquímica de la colección. prevAll() y nextAll() recuperan, respectivamente, los hermanos (siblings) mayores y menores (o anteriores y siguientes, como se prefiera). slice() corta la colección a gusto del consumidor. hasClass('una-clase‘) nos dice si el elemento tiene o no asignada una-clase. andSelf() combina dos colecciones apiladas. contents() recupera los nodos hijos, incluidos los nodos de tipo texto.

Manipulación

Llegan wrapAll() y wrapInner(). clone() trae una gran novedad: usando clone(true) los elementos clonados mantienen los manejadores de eventos del original.

Posición

Aterriza offset(), que nos devuelve las coordenadas de un elemento tomando como origen la esquina superior izquierda del viewport. height() y width() también sirven ahora para obtener el tamaño de la ventana y el documento.

AJAX

Ahora load() permite cargar de modo muy sencillo pedazos de HTML. Usa un selector a continuación de la URL para indicar el filtro: $('#links').load('/Main_Page #p-Getting-Started li'). Con getScript() podemos cargar scripts desde otros dominios, lo que autoriza a getJSON() a utilizar servicios web basados en JSONP. El método serialize() ha sido reescrito para permitir la serialización sencilla de formularios. Se ha incorporado a $.ajax() la opción cache que fuerza el refresco de los datos solicitados.

Efectos

Ya podemos utilizar valores en em o porcentajes en las animaciones. El plugin (oficial) Color Animations permite realizar animaciones de colorines. stop() detiene las animaciones. Llegan stop(), queue(), dequeue(), las animaciones relativas, las personalizadas y otras maravillas.

Eventos

El nuevo método triggerHandler() dispara los manejadores de eventos asignados a un elemento sin activar el comportamiento por defecto del elemento. Llegan los eventos con espacio de nombres.

Todos los detalles en jQuery 1.2: jQuery.extend(”Awesome”).

Chuleta de jQuery

Adrien Gibrat ha tenido a bien crear y compartir una chuletita de jQuery (PDF).

Novedades en jQuery 1.1.4

La versión 1.1.4 de jQuery, publicada a finales de agosto, incluye, como es costumbre, algunas mejoras en el rendimiento, pero también (y esto no es tan habitual) algunas novedades interesantes que merece la pena conocer.

Continúa leyendo Novedades en jQuery 1.1.4

Acerca de Scriptia

Saltar a la caja de búsqueda

Scriptia forma parte del PDM de Choan C. Gálvez, desarrollador web residente en Barcelona. Scriptia pretende mejorar la calidad de la documentación acerca de javascript disponible en español.